/* Editor.cpp
Copyright (c) 2008 Michael Zahniser
Please note the license terms (MIT license) at the end of this document.

Function definitions for the Editor class.
*/

#include "Editor.h"

#include <algorithm>

using std::lower_bound;

const int Editor::COLUMNS = 16;
const int Editor::ROWS = 20;



// Default constructor:
// Sets up the key event map and "loads" an empty file.
Editor::Editor()
	: isDone(false)
{
	// Start with a blank file.
	Load("");

	handler.SetClient(this);
	handler(SDLK_UNKNOWN, KMOD_SHIFT) =
		&Editor::OnKey;
	handler(SDLK_UNKNOWN, KMOD_NONE) =
		&Editor::OnKey;
	handler('q', KMOD_CTRL) =
		&Editor::OnQuit;
	handler(SDLK_ESCAPE, KeyHandler<Editor>::ANY_MOD) =
		&Editor::OnEscape;
	handler('e', KMOD_SHIFT) =
		&Editor::OnEndianToggle;
	handler(SDLK_LEFT, KeyHandler<Editor>::ANY_MOD) =
		&Editor::OnLeft;
	handler(SDLK_RIGHT, KeyHandler<Editor>::ANY_MOD) =
		&Editor::OnRight;
	handler(SDLK_UP, KeyHandler<Editor>::ANY_MOD) =
		&Editor::OnUp;
	handler(SDLK_DOWN, KeyHandler<Editor>::ANY_MOD) =
		&Editor::OnDown;
	handler(' ', KeyHandler<Editor>::ANY_MOD) =
		&Editor::OnNext;
	handler(SDLK_BACKSPACE, KeyHandler<Editor>::ANY_MOD) =
		&Editor::OnDelete;
	handler(SDLK_DELETE, KeyHandler<Editor>::ANY_MOD) =
		&Editor::OnDelete;
	handler(SDLK_RETURN, KeyHandler<Editor>::ANY_MOD) =
		&Editor::OnBreak;
}



// Load:
// Load the file from the given path.
void Editor::Load(const string &filePath)
{
	document.Load(filePath);

	// Begin scrolled so that the cursor is visible.
	CalculateLines(document.Begin());
	ScrollToCursor();
	cursorCell = CellOf(document.Cursor());
}



// Handle:
// Handle the given SDL key event.
void Editor::Handle(SDLKey key, SDLMod mod)
{
	// Skip modifier keys.
	if(key == SDLK_RSHIFT || key == SDLK_LSHIFT || key == SDLK_RCTRL || key == SDLK_LCTRL || 
			key == SDLK_RALT || key == SDLK_LALT || key == SDLK_RMETA || key == SDLK_LMETA)
		return;

	if(key == SDLK_PAGEUP)
		ScrollToRow(ScrollRow() - (ROWS - 2));
	else if(key == SDLK_PAGEDOWN)
		ScrollToRow(ScrollRow() + (ROWS - 2));
	else if(key == SDLK_HOME)
		ScrollToRow(0);
	else if(key == SDLK_END)
		ScrollToRow(TotalRows());
	else
	{
		handler.Handle(key, mod);
		ScrollToCursor();
	}
}



// IsDone:
// This flag is set when the user sends a quit command.
bool Editor::IsDone() const
{
	return isDone;
}



// GetDocument:
// Get the document that is being edited. Only Editor may change it,
// but others may view it.
const Document &Editor::GetDocument() const
{
	return document;
}



// Lines:
// Get the line start iterators. This vector has one sentinel iterator at its end
// that points to past the end of the document.
const vector<Document::It> &Editor::Lines() const
{
	return lines;
}



// CellOf:
// Get the cell containing the given data.
// If the returned cell's column is -1, it is not visible.
Editor::Cell Editor::CellOf(Document::It it) const
{
	Cell cell;
	
	if(it < lines.front())
	{
		cell.row = -1;
		cell.column = -1;
	}
	else if(it >= lines.back())
	{
		cell.row = VisibleRows();
		cell.column = -1;
	}
	else
	{
		vector<Document::It>::const_iterator line = upper_bound(lines.begin(), lines.end(), it);
		--line;
		cell.row = static_cast<short>(line - lines.begin());
		cell.column = static_cast<short>(it - *line);
	}
	
	return cell;
}



// PlaceCursor:
// Place the cursor in the given cell position, keeping the same anchor if
// keepAnchor is true.
void Editor::PlaceCursor(Cell cell, bool keepAnchor)
{
	cursorCell = cell;
	
	// Check the bounds.
	if(cursorCell.row < 0)
		cursorCell.row = 0;
	if(cursorCell.row >= ROWS)
		cursorCell.row = ROWS - 1;
	
	if(cursorCell.column < 0)
		cursorCell.column = 0;
	if(cursorCell.column >= COLUMNS)
		cursorCell.column = COLUMNS - 1;

	PlaceCursor(keepAnchor);
}



// ScrollToRow:
// Scroll to put the given row of the document at the top of the view.
// If the given value is out of range, it will be cropped appropriately.
void Editor::ScrollToRow(int row)
{
	// Make sure the row is in the acceptable range.
	if(row < 0)
		row = 0;
	else
	{
		int maxRow = (TotalRows() - VisibleRows());
		if(row > maxRow)
			row = maxRow;
	}

	// Find the first break _after_ the given row.
	Document::It previous = document.Begin();
	set<Document::It>::const_iterator it = document.Breaks().begin();
	
	int rows = 0;
	for( ; it != document.Breaks().end() && rows < row; ++it)
	{
		// Divide the number of rows between breaks by the number of characters
		// per column (round up).
		rows += RowsBetween(previous, *it);
		previous = *it;
	}
	previous += (row - rows) * COLUMNS;
	
	CalculateLines(previous);
}



// VisibleRows:
// Get the number of visible rows of data.
// It may be less than the number of rows in the display if the file is small.
// So it isn't safe, for example, to assume that lines[ROWS] exists, but
// lines[VisibleRows()] will always be the sentinel at the end of lines.
int Editor::VisibleRows() const
{
	return static_cast<int>(lines.size() - 1);
}



// TotalRows:
// Get the total number of rows of data.
int Editor::TotalRows() const
{
	return RowsBefore(document.End());
}



// ScrollRow:
// Get the row to which the display is currently scrolled.
int Editor::ScrollRow() const
{
	return RowsBefore(lines.front());
}



// CalculateLines:
// Calculate line offsets, taking any line breaks into account.
// Scroll such that the given character is at the top of the screen
void Editor::CalculateLines(Document::It line)
{
	lines.clear();
	lines.push_back(line);
	
	set<Document::It>::const_iterator it = document.Breaks().upper_bound(line);
	set<Document::It>::const_iterator end = document.Breaks().end();
	
	// Include one extra line, the end of the view, as a sentinel.
	for(int row = 0; row < ROWS && line < document.End(); ++row)
	{
		// Ordinarily, just add COLUMNS to the line each time.
		line += COLUMNS;
		// But if there is a break in this current line, move to the break instead.
		if(it != end && line >= *it)
		{
			line = *it;
			++it;
		}
		lines.push_back(line);
	}
}



// ScrollToCursor:
// Scroll so that the cursor is on screen. If the cursor is in the current view,
// do nothing. If it is above the current view, scroll so that it is in the top
// line. If below, scroll so that it is in the bottom line.
void Editor::ScrollToCursor()
{
	Document::It cursor = document.Cursor();
	
	if(VisibleRows() < ROWS || (cursor >= lines.front() && cursor < lines.back()))
		return;
	
	if(cursor < lines.front())
	{
		// Scroll up so that the cursor is in the top row.
		Document::It previousBreak = document.Begin();

		// If any of the breaks comes before the current cursor position, it affects
		// the cursor cell.
		set<Document::It>::const_iterator it = document.Breaks().upper_bound(cursor);
		if(it != document.Breaks().begin())
			previousBreak = *--it;
		
		// Get the number of characters from the previous break to the cursor's row.
		size_t charsFromBreak = cursor - previousBreak;
		// Round down to the nearest whole number of rows.
		charsFromBreak -= charsFromBreak % COLUMNS;
		// Recalculate the lines starting at that position.
		CalculateLines(previousBreak + charsFromBreak);
	}
	else
	{
		// Scroll down so that the cursor is in the bottom row.
		Document::It previousBreak = document.Begin();
		
		int row = ROWS;
		set<Document::It>::const_iterator it = document.Breaks().upper_bound(cursor);
		Document::It currentRow = cursor;
		while(row > 0)
		{
			// If any of the breaks comes before the current cursor position, it affects
			// the cursor cell.
			if(it != document.Breaks().begin())
				previousBreak = *--it;
			else
				previousBreak = document.Begin();
			
			// Round the number of rows between breaks up.
			row -= RowsBetween(previousBreak, currentRow);
			currentRow = previousBreak;
		}
		
		// Now, row <= 0. Add rows from the previous break to make the row 0.
		CalculateLines(currentRow - row * COLUMNS);
		// TODO: Find a more elegant way of handling this special case.
		if(cursor == document.End())
			ScrollDown();
	}
}



// ScrollUp:
// Scroll up (updating the lines vector in the process).
bool Editor::ScrollUp()
{
	// We can always scroll up unless we're at the top of the document.
	Document::It line = lines.front();
	if(line == document.Begin())
		return false;
		
	// Get the  first line break before the first line.
	set<Document::It>::const_iterator it = document.Breaks().lower_bound(line);
	line -= COLUMNS;
	if(line < document.Begin())
		line = document.Begin();
	if(it != document.Breaks().begin())
	{
		--it;
		if(line < *it)
			line = *it;
	}
	
	lines.pop_back();
	lines.insert(lines.begin(), line);
	
	return true;
}



// ScrollDown:
// Scroll down (updating the lines vector in the process).
bool Editor::ScrollDown()
{
	Document::It line = lines.back();
	if(line > document.End())
		return false;
	
	// Get the first break after the last line of the view.
	set<Document::It>::const_iterator it = document.Breaks().upper_bound(line);
	line += COLUMNS;
	if(it != document.Breaks().end() && *it < line)
		line = *it;
	else if(line > document.End())
		line = document.End() + 1;

	lines.erase(lines.begin());
	lines.push_back(line);
	
	return true;
}



// UpdateCursorCell:
// Update the cursor position based on the document.
void Editor::UpdateCursorCell()
{
	cursorCell = CellOf(document.Cursor());
}



// PlaceCursor:
// Move the cursor in the document to match the cursor cell position.
void Editor::PlaceCursor(bool keepAnchor)
{
	// Find the maximum column of the data in this row.
	int length = static_cast<int>(lines[cursorCell.row + 1] - lines[cursorCell.row]) - 1;
	// If the cursor comes before that column, put the cursor in its proper place.
	// Otherwise, put the cursor on the maximum column.
	if(length > cursorCell.column)
		length = cursorCell.column;
		
	document.SetCursor(lines[cursorCell.row] + length, keepAnchor);
}



// RowsBefore:
// Count the number of rows of the document up to the given point.
int Editor::RowsBefore(Document::It end) const
{
	// Determine the total number of rows.
	Document::It previous = document.Begin();
	set<Document::It>::const_iterator it = document.Breaks().begin();
	
	int rows = 0;
	for( ; it != document.Breaks().end() && *it < end; ++it)
	{
		// Divide the number of rows between breaks by the number of characters
		// per column (round up).
		rows += RowsBetween(previous, *it);
		previous = *it;
	}
	rows += RowsBetween(previous, end);
	
	return rows;
}



// RowsBetween:
// Count the number of rows between two iterators.
int Editor::RowsBetween(Document::It first, Document::It last) const
{
	return static_cast<int>(((last - first) + (COLUMNS - 1)) / COLUMNS);
}



// OnEndianToggle:
void Editor::OnEndianToggle(SDLKey key, SDLMod mod)
{
	Type::SetBigEndian(!Type::IsBigEndian());
}



// OnLeft:
void Editor::OnLeft(SDLKey key, SDLMod mod)
{
	document.SetCursor(document.Cursor() - 1, ((mod & KMOD_SHIFT) != 0));
	UpdateCursorCell();
}



// OnRight:
void Editor::OnRight(SDLKey key, SDLMod mod)
{
	document.SetCursor(document.Cursor() + 1, ((mod & KMOD_SHIFT) != 0));
	UpdateCursorCell();
}



// OnUp:
void Editor::OnUp(SDLKey key, SDLMod mod)
{
	ScrollToCursor();
	
	// Move the cursor to the same column of the previous row.
	if(!cursorCell.row)
	{
		// If we're on the first row and try to move the cursor up,
		// move it to the start of the row.
		if(!ScrollUp())
			cursorCell.column = 0;
	}
	else
		--cursorCell.row;
	
	// Place the cursor in the document at the location corresponding to
	// the current cursor cell.
	PlaceCursor((mod & KMOD_SHIFT) != 0);
}



// OnDown:
void Editor::OnDown(SDLKey key, SDLMod mod)
{
	ScrollToCursor();

	// Move the cursor to the same column of the next row.
	if(cursorCell.row >= VisibleRows() - 1)
	{
		// If we're on the last row and try to move the cursor down,
		// move it to the end of the row.
		if(!ScrollDown())
			cursorCell.column = static_cast<int>(document.End() - lines[cursorCell.row]);
	}
	else
		++cursorCell.row;
	
	// Place the cursor in the document at the location corresponding to
	// the current cursor cell.
	PlaceCursor((mod & KMOD_SHIFT) != 0);
}



// OnNext:
void Editor::OnNext(SDLKey key, SDLMod mod)
{
	document.NextCursor((mod & KMOD_SHIFT) != 0);
	UpdateCursorCell();
}



// OnDelete:
void Editor::OnDelete(SDLKey key, SDLMod mod)
{
	document.Handle('\b', false);
	CalculateLines(lines.front());
	UpdateCursorCell();
}



// OnBreak:
void Editor::OnBreak(SDLKey key, SDLMod mod)
{
	document.Handle('\n', false);
	CalculateLines(lines.front());
	UpdateCursorCell();
}



// OnQuit:
void Editor::OnQuit(SDLKey key, SDLMod mod)
{
	document.Save();

	OnEscape(key, mod);
}



// OnEscape:
// Exit the program without saving.
void Editor::OnEscape(SDLKey key, SDLMod mod)
{
	isDone = true;
}



// OnKey:
// Handle the given keypress. Return true if the program should quit.
void Editor::OnKey(SDLKey key, SDLMod mod)
{
	if(key < 128)
		document.Handle(static_cast<char>(key), ((mod & KMOD_SHIFT) != 0));
}



/* Copyright (c) 2008 Michael Zahniser

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE. */
